//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Base/Include/Common.h"

namespace Engine
{
namespace Base
{


	//
	// Geographic Location
	//

	struct GeoLocation
	{
	// types
	public:
		typedef GeoLocation		Self;


	// variables
	private:
		double	_lon;	// x	-Pi..+Pi
		double	_lat;	// y	-Pi/2..+Pi/2


	// methods
	public:
		GeoLocation () : _lat(0.0), _lon(0.0)
		{}

		bool	operator == (const Self &right) const	{ return All( ToVec2() == right.ToVec2() ); }
		bool	operator != (const Self &right)	const	{ return not ( *this == right ); }

		double const &	Longitude ()	const	{ return _lon; }
		double const &	Latitude ()		const	{ return _lat; }

		Self &	FromDegrees (double lon, double lat)
		{
			_lon = DTR( lon );
			_lat = DTR( lat );
			return *this;
		}

		Self &	FromRadians (double lon, double lat)
		{
			_lon = lon;
			_lat = lat;
			return *this;
		}

		dvec2 const &	ToVec2 () const
		{
			return ReferenceCast< dvec2 >( *this );
		}

		dvec2	ToDegrees () const
		{
			return RTD( ToVec2() );
		}

		double	Distance (const Self &loc) const
		{
			// retern distance in meters
			return _SphericalLawOfCosines( *this, loc );
		}

		dvec2	Vector (const Self &loc) const
		{
			// return vector in meters
			const double	d  = Distance( loc );
			const dvec2		sc = _HeadingSinCos( *this, loc );
			return sc.Normalized().yx() * d;
		}

		double	Heading (const Self &loc) const
		{
			const dvec2	sc = _HeadingSinCos( *this, loc );
			return ATan( sc.y, sc.x );
		}

		Self &	Validate ()
		{
			_lon = Mod( _lon, Pi<double>() );
			_lat = Mod( _lat, Pi<double>() * 0.5 );
			return *this;
		}


		// const
		static constexpr	double	GetEarthMeanRadius ()			{ return 6371.0e+3; }
		static constexpr	double	GetEarthEquatorialRadius ()		{ return 6378.1e+3; }
		static constexpr	double	GetEarthPolarRadius ()			{ return 6356.8e+3; }


	//private:
		static double _Haversine (const Self &left, const Self &right)
		{
			const dvec2		s = Square( Sin( ( right.ToVec2() - left.ToVec2() ) * 0.5 ) );
			const double	a = s.y + s.x * Cos( left.Latitude() ) * Cos( right.Latitude() );
			return GetEarthMeanRadius() * 2.0 * ATan( Sqrt( a ), Sqrt( 1 - a ) );
		}


		static double _SphericalLawOfCosines (const Self &left, const Self &right)
		{
			return ACos( Sin( left.Latitude() ) * Sin( right.Latitude() ) +
						 Cos( left.Latitude() ) * Cos( right.Latitude() ) *
						 Cos( right.Longitude() - left.Longitude() ) ) * GetEarthMeanRadius();
		}


		static double _EquirectangularApproximation (const Self &left, const Self &right)
		{
			return _EquirectangularApproximationVec( left, right ).Length();
		}


		static dvec2 _EquirectangularApproximationVec (const Self &left, const Self &right)
		{
			const double	x = ( right.Longitude() - left.Longitude() ) *
								Cos( ( left.Latitude() + right.Latitude() ) * 0.5 );
			const double	y = ( right.Latitude() - left.Latitude() );
			return dvec2( x, y ) * GetEarthMeanRadius();
		}


		static dvec2 _HeadingSinCos (const Self &left, const Self &right)
		{
			const dvec2		lon_sc = SinCos( right._lon - left._lon );
			const double	lat_c  = Cos( right._lat );
			const dvec2		lat_sc = SinCos( left._lat );

			return dvec2( lat_sc.y * Sin( right._lat ) - lat_sc.x * lat_c * lon_sc.y,
						  lon_sc.x * lat_c );
		}
	};


}	// Base
}	// Engine
